using System;
using System.Collections;
using System.Data;
using BO = PHSRAG.Insight21.BusinessObjects;
using Utility = PHSRAG.Utility;
using ICachePolicy = PHSRAG.Insight21.Policy.ICachePolicy;
using DataAccess = PHSRAG.Utility.DataAccess;

namespace PHSRAG.Insight21.FormsManagement
{
	/// <summary>
	/// QuestionnaireAnswer holds all of the answers to questions that form a questionnaire. Each answer to a question is inturn an Answer object.
	/// 
	/// The 'name' variable is used in cases where a questionnaire can be answered multiple times within a process. For ex: the Drugs
	/// questionnaire can be answered multiple times, once for each drug. If the name of the drug is tylenol, then the 'name' instance variable
	/// on this class would hold that name.
	/// </summary>
	public class QuestionnaireAnswer : BO.SmartBusinessObject, System.ICloneable
	{
		#region instance variables
		private int questionnaireVersionId;
		private string name;
		private ArrayList answers;
		private string questionnaireCategory;
		private bool required;
		private bool editable;
		private bool allowMultiple;
		private QuestionnaireVersion questionnaireVersion;
		private string lastModifiedDate;
		private int externalId;
		private int action = (int)UserAction.Edit;
		private bool updateModifiedDate;
		private bool highlight;
		private int lastModifiedByAdmPersonId;
		private string lastModifiedBy;
		private string status;
		private string statusMeaning;
		#endregion

		#region Constants
		private const string QuestionnaireAnswerKey = "QuestionnaireAnswer_E9C382B9-8191-4504-9F01-93E29601C9F3";
		private const string QuestionnaireAnswerDataTableName = "QuestionnaireAnswer";

		/// <summary>
		///  QV dataTable Columns
		/// </summary>
		private const string ColumnId = "Id";
		private const string ColumnName = "Name";
		private const string ColumnQuestionnaireVersionId = "QuestionnaireVersionId";
		private const string ColumnquestionnaireCategory = "questionnaireCategory";
		private const string ColumnRequired = "Required";
		private const string ColumnEditable = "Editable";
		private const string ColumnLastModifiedDate = "LastModifiedDate";
		private const string ColumnExternalId = "ExternalId";
		private const string ColumnIsClone = "IsClone";
		private const string ColumnActionType = "ActionTypeId";
		private const string ColumnHightlight = "Highlight";
		private const string ColumnLastModifiedByAdmPersonId = "LastModifiedByAdmPersonId";
		private const string ColumnLastModifiedBy = "LastModifiedBy";
		private const string ColumnStatus = "Status";
		private const string ColumnStatusMeaning = "StatusMeaning";
		#endregion

		#region Properties
		/// <summary>
		/// Cache Key uniquely identifies this object within the cache.
		/// </summary>
		/// <returns>the unique string for this object.</returns>
		public override string CacheKey()
		{
			return QuestionnaireAnswerKey;
		}

		/// <summary>
		/// QuestionnaireVersion this QuestionnaireAnswer relates to.
		/// </summary>
		public int QuestionnaireVersionId
		{
			get { return questionnaireVersionId; }
			set { SetDirty(ref questionnaireVersionId, value); }
		}

		/// <summary>
		/// Name of the questionnaire Version. Name could be the name of the questionnaire,
		/// or the instance of the questionnaire (ex. Advil) if it refers to the Drug Questionnaire.
		/// </summary>
		public string Name
		{
			get { return name; }
			set { SetDirty(ref name, value); }
		}

		/// <summary>
		/// The Parent Questionnaire's category (Intervention, Special Population, etc.)
		/// NOTE: Set doesn't set the Dirty flag, because this is a non-persistent field.
		/// The set is available however for the AddDeleteForms page, which uses the category
		/// when adding a new QuestionnaireAnswer for the grid that hasn't been saved yet. 
		/// The page uses the category to group the QuestionnaireAnswers by (headings)
		/// </summary>
		public string QuestionnaireCategory
		{
			get { return questionnaireCategory; }
			set { questionnaireCategory = value; }
		}

		/// <summary>
		/// Whether or not this QuestionnaireAnswer is required for a study, 
		/// so it cannot be removed in the Add/Delete forms page.
		/// </summary>
		public bool Required
		{
			get { return required; }
			set { SetDirty(ref required, value); }
		}

		/// <summary>
		///Whether this QA will be edited or not. This is primarily for the Amendment 
		///quesitonnaire- after answering the AM questionnaire, the AM req. script will 
		///set this flag to true on any sections the user wishes to edit. This flag is 
		///really used on the ProtocolPRocessOverview screen of an amendment to determine 
		///which questionnaires to display.
		/// </summary>
		public bool Editable
		{
			get { return editable; }
			set { SetDirty(ref editable, value); }
		}

		/// <summary>
		/// External system reference Id- in the case where the QA's Questionnaire is the 
		/// drug, device or sponsor questionnaire, we use this ExternalId field to store the
		/// Rex (or other system)'s Id in this field. Ex. Drug with ID 123 is selected from teh 
		/// drug selection page from Rex, the ExternalId would be 123. 
		/// </summary>
		public int ExternalId
		{
			get { return externalId; }
			set { SetDirty(ref externalId, value); }
		}

		/// <summary>
		/// readonly property of when this questionnaire was last modified.
		/// this is set within the database, upon updates. It gets inserted as null.
		/// </summary>
		public string LastModifiedDate
		{
			get { return lastModifiedDate; }
		}

		/// <summary>
		/// The action being taken on the questionnaireAnswer (none, 
		/// edit, remove from study)
		/// </summary>
		public int Action
		{
			get { return action; }
			set { SetDirty(ref action, value); }
		}

		/// <summary>
		/// Collection of Answer objects that belong to this QuestionnaireAnswer.
		/// </summary>
		public ArrayList Answers
		{
			get
			{
				if (answers == null)
				{
					answers = Answer.GetAnswers(Id, CachePolicy);
				}
				return answers;
			}

			set { answers = value; }
		}

		/// <summary>
		/// This tells the page / user whether or not any of the questions
		/// for this particular section have been answered, based on the 
		/// count of answers for that section.
		/// </summary>
		public bool HaveAnswered
		{
			get { return (Answers.Count != 0); }
		}


		/// <summary>
		///  the actual questionnaireVersion object this QA pertains to
		/// </summary>
		public QuestionnaireVersion QuestionnaireVersion
		{
			get
			{
				if (questionnaireVersion == null && questionnaireVersionId != 0)
				{
					questionnaireVersion = new QuestionnaireVersion(CachePolicy);
					questionnaireVersion.Load(Utility.DataAccess.GetConnectionString(), questionnaireVersionId);
				}
				return questionnaireVersion;
			}

			set { questionnaireVersion = value; }
		}

		/// <summary>
		/// Wrap the multiple flag of the QuestionnaireVersion, unless there isn't a 
		/// questionnaireversion Id tied to this QA (in the case of the Add/Delete Forms
		/// page where we're creating a fake QA that contains the category and adding muliple
		/// options here.
		/// </summary>
		public bool VersionAllowsMultiple
		{
			get { return (QuestionnaireVersion != null && QuestionnaireVersion.Questionnaire != null) ? QuestionnaireVersion.Questionnaire.AllowMultiple : allowMultiple; }
			set { allowMultiple = value; }
		}


		/// <summary>
		/// Whether or not this QuestionnaireAnswer is saved after answering the questions. If so,
		/// the date modified should be updated while inserting the QA. 
		/// In situations where the QA is cloned, even though the answers for the questionnaires are
		/// present in the clone, it is not ideal to show the last modified date as they are really
		/// not modified after cloning. In such cases while inserting the QA clone the date modified 
		/// should not be updated.
		/// </summary>
		public bool UpdateModifiedDate
		{
			get { return updateModifiedDate; }
			set { SetDirty(ref updateModifiedDate, value); }
		}

		/// <summary>
		/// Indicates whether or not the Questionnaire Answer is of some importance.
		/// Note: The highlight attribute is reset while cloning
		/// </summary>
		public bool Highlight
		{
			get { return highlight; }
			set { SetDirty(ref highlight, value); }
		}

		/// <summary>
		/// AdmPersonId of the Person who Completed/Modified the form(Questionnaire Answer)
		/// 
		/// </summary>
		public int LastModifiedByAdmPersonId
		{
			get { return lastModifiedByAdmPersonId; }
			set { SetDirty(ref lastModifiedByAdmPersonId, value); }
		}

		/// <summary>
		/// AdmPersonId of the Person who Completed/Modified the form(Questionnaire Answer)
		/// 
		/// </summary>
		public string LastModifiedBy
		{
			get { return lastModifiedBy; }
		}

		/// <summary>
		/// Gets or sets the status(refcode) of the questionnaire answer
		/// </summary>
		public string Status
		{
			get { return status; }
			set { SetDirty(ref status, value); }
		}

		/// <summary>
		/// Gets or sets the status(description) of the questionnaire answer
		/// </summary>
		public string StatusMeaning
		{
			get { return statusMeaning; }
			set { statusMeaning = value; }
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor called when creating a new QuestionnaireAnswer.
		/// </summary>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		public QuestionnaireAnswer(ICachePolicy cachePolicy)
			: base(cachePolicy)
		{
		}

		/// <summary>
		/// Create the object based on the passed in row.
		/// </summary>
		/// <param name="cachePolicy">The prevalent cache policy</param>
		/// <param name="row">DataRow object holding the values for instance variables</param>
		public QuestionnaireAnswer(ICachePolicy cachePolicy, DataRow row)
			: base(cachePolicy)
		{
			PopulateDataMembers(row);
		}
		#endregion

		#region Overridden methods
		public override bool Equals(object obj)
		{
			if (obj == null)
			{
				return false;
			}

			PHSRAG.Insight21.FormsManagement.QuestionnaireAnswer oQ = obj as PHSRAG.Insight21.FormsManagement.QuestionnaireAnswer;
			if ((object)oQ == null)
			{
				return false;
			}

			return (oQ.Name == Name);
		}

		public bool Equals(QuestionnaireAnswer obj)
		{
			if ((object)obj == null)
			{
				return false;
			}

			return (obj.Name == Name);
		}

		public override int GetHashCode()
		{
			return base.GetHashCode();
		}

		/// <summary>
		/// Checks dirtystate of the object.
		/// </summary>
		/// <returns>returns true if it or any of its contained objects are dirty.</returns>
		public override bool IsDirty()
		{
			if (Dirty)
				return true;

			foreach (Answer answer in Answers)
			{
				if (answer.IsDirty())
					return true;
			}

			return false;
		}

		/// <summary>
		/// Deletes the record represented by the object from the database
		/// </summary>
		/// <param name="sqlTransaction">transaction to use when deleting, when this is implemented.</param>
		protected override void DeleteObject(System.Data.SqlClient.SqlTransaction sqlTransaction)
		{
			// delete the contained answers first
			foreach (Answer answer in Answers)
			{
				answer.Delete(sqlTransaction);
			}

			DataAccess.ExecuteScalar(sqlTransaction.Connection, sqlTransaction, "DeleteQuestionnaireAnswer",
					DataAccess.BuildSqlParameterArray("@id", SqlDbType.Int, Id));
		}

		/// <summary>
		///  Saves the object, persisting it and any of it's contained objects into the database.
		/// </summary>
		/// <param name="sqlTransaction">transaction to use to save.</param>
		protected override void SaveObject(System.Data.SqlClient.SqlTransaction sqlTransaction)
		{
			Id = Convert.ToInt32(DataAccess.ExecuteScalar(sqlTransaction.Connection, sqlTransaction, "SaveQuestionnaireAnswer",
				DataAccess.BuildSqlParameterArray(
				"@id", SqlDbType.Int, Id,
				"@name", SqlDbType.VarChar, Name,
				"@questionnaireVersionId", SqlDbType.Int, QuestionnaireVersionId,
				"@required", SqlDbType.Bit, Required,
				"@editable", SqlDbType.Bit, Editable,
				"@externalId", SqlDbType.Int, ExternalId,
				"@isClone", SqlDbType.Bit, IsClone,
				"@actionId", SqlDbType.Int, Action,
				"@updateModifiedDate", SqlDbType.Bit, updateModifiedDate,
				"@highlight", SqlDbType.Bit, highlight,
				"@status", SqlDbType.VarChar, (string.IsNullOrEmpty(status) ? (object)DBNull.Value : status),
				"@lastModifiedByAdmPersonId", SqlDbType.Int, (lastModifiedByAdmPersonId == 0) ? (object)DBNull.Value : lastModifiedByAdmPersonId)));


			if (answers != null)
			{
				//Saving Answer objects
				//Answers could be marked as deleted. The following logic will call Delete on objects marked for deletion
				//and remove them from the collection and as such a foreach loop is not used. The variable 'index' is a pointer
				//to the object in the collection that is being processed. The 'count' keeps track of the number of objects in 
				//the collection. The count will change whenever there is an object that is marked for deletion.
				int index = 0;
				int count = answers.Count;
				while (index < count)
				{
					Answer answer = (Answer)answers[index];
					answer.QuestionnaireAnswerId = Id;
					answer.Save(sqlTransaction);
					if (!answer.MarkForDelete)
					{
						++index;
					}
					else
					{
						answers.RemoveAt(index);
						--count;
					}
				}
			}
		}

		/// <summary>
		///  Loads the object given an ID.
		/// </summary>
		/// <param name="connectionString">Connectionstring that points to the database.</param>
		/// <param name="args">args.</param>
		public override void Load(string connectionString, params object[] args)
		{
			if (args.Length != 1)
				throw new Exception("QuestionnaireAnswer.Load expects ID only.");

			try
			{
				DataSet newDataSet = Utility.DataAccess.GetDataSet(connectionString, "GetQuestionnaireAnswer",
					Utility.DataAccess.BuildSqlParameterArray("@id", SqlDbType.Int, (int)args[0]));
				if (newDataSet.Tables.Count > 0)
				{
					if (newDataSet.Tables[0].Rows.Count > 0)
					{
						PopulateDataMembers(newDataSet.Tables[0].Rows[0]);
					}
				}
				else
				{
					throw new Exception("GetQuestionnaireAnswer stored procedure returned incorrect results");
				}
			}
			catch (Exception ex)
			{
				Utility.ExceptionReport.WriteToEventLog("QuestionnaireAnswer Load", ex, true, Utility.ExceptionReport.ReportType.Error);
				throw new Exception("QuestionnaireAnswer Load", ex);
			}
		}

		/// <summary>
		/// Cache this instance using the prevalent cache policy.
		/// </summary>
		public override void Cache()
		{
			CachePolicy.Cache(QuestionnaireAnswerKey, this);
		}

		/// <summary>
		/// Remove this instance from the cache using the prevalent cache policy.
		/// </summary>
		public override void UnCache()
		{
			CachePolicy.UnCache(QuestionnaireAnswerKey);
		}


		/// <summary>
		/// Populates instance fields with corresponding values in the datarow
		/// </summary>
		/// <param name="row">DataRow holding the values to be assigned to instance variables</param>
		private void PopulateDataMembers(DataRow row)
		{
			object o = row[ColumnId];
			Id = (o == DBNull.Value) ? 0 : (int)o;
			name = (string)row[ColumnName];
			questionnaireCategory = (string)row[ColumnquestionnaireCategory];
			questionnaireVersionId = (int)row[ColumnQuestionnaireVersionId];
			o = row[ColumnEditable];
			editable = (o == null) ? true : Convert.ToBoolean(o);
			o = row[ColumnRequired];
			required = (o == null) ? true : Convert.ToBoolean(o);
			o = row[ColumnLastModifiedDate];
			lastModifiedDate = (o == DBNull.Value) ? string.Empty : ((DateTime)o).ToString();
			o = row[ColumnExternalId];
			externalId = (o == DBNull.Value) ? 0 : (int)o;
			o = row[ColumnIsClone];
			isClone = (o == DBNull.Value) ? false : Convert.ToBoolean(o);
			o = row[ColumnActionType];
			action = (o == DBNull.Value) ? (int)UserAction.Edit : (int)o;

			highlight = Convert.ToBoolean(row[ColumnHightlight]);
			o = row[ColumnLastModifiedByAdmPersonId];
			lastModifiedByAdmPersonId = (o == DBNull.Value) ? 0 : (int)o;
			o = row[ColumnLastModifiedBy];
			lastModifiedBy = (o == DBNull.Value) ? string.Empty : (string)o;

			if (row.Table.Columns.Contains(ColumnStatus))
			{
				o = row[ColumnStatus];
				status = (o == DBNull.Value) ? string.Empty : (string)o;

				o = row[ColumnStatusMeaning];
				statusMeaning = (o == DBNull.Value) ? string.Empty : (string)o;
			}
		}
		#endregion

		#region Static Methods.

		#endregion

		#region Public Methods
		/// <summary>
		/// Gets the answers for the specified question
		/// </summary>
		/// <param name="questionnaireItemName">Name of the question</param>
		/// <returns>A collection of Answer objects</returns>
		public ArrayList GetAnswers(string questionnaireItemName)
		{
			ArrayList list = new ArrayList();
			foreach (Answer answer in Answers)
			{
				GetAnswers(list, questionnaireItemName, answer);
			}
			return list;
		}

		/// <summary>
		/// Helper function to be used by scripting to easily pull
		/// out the answer to a question
		/// </summary>
		/// <param name="questionnaireItemName">Question Name</param>
		/// <returns>Answer Value</returns>
		public string GetAnswerValue(string questionnaireItemName)
		{
			ArrayList list = GetAnswers(questionnaireItemName);
			if (list.Count > 0)
			{
				return ((Answer)list[0]).Value;
			}

			return string.Empty;
		}

		#endregion

		#region Private Methods
		/// <summary>
		/// Recursively loops through 'answer' and the its child answers looking for an answer to 
		/// question with the specified name(questionnaireItemName). If an answer is found, then it is
		/// added to the arraylist(list)
		/// </summary>
		/// <param name="list">Collection of answers for the specified question</param>
		/// <param name="questionnaireItemName">The name of the question</param>
		/// <param name="answer">The answer object that is being verified</param>
		private void GetAnswers(ArrayList list, string questionnaireItemName, Answer answer)
		{
			if (answer.Name == questionnaireItemName)
			{
				list.Add(answer);
			}

			if (answer.ChildAnswers != null)
			{
				foreach (Answer childAnswer in answer.ChildAnswers)
				{
					GetAnswers(list, questionnaireItemName, childAnswer);
				}
			}
		}

		/// <summary>
		/// Creates a clone of each answer from the answer collection and adds it to the questionnaireAnswer
		/// </summary>
		/// <param name="questionnaireAnswerClone">Clone of the QuestionnaireAnswer being created</param>
		/// <param name="answers">List of answers that need to be copied to the questionnaire answer</param>
		private void CopyAnswers(QuestionnaireAnswer questionnaireAnswerClone, ArrayList answers)
		{
			foreach (Answer answer in answers)
			{
				Answer answerClone = answer.Clone();
				answerClone.Id = 0;
				answerClone.QuestionnaireAnswerId = 0;
				questionnaireAnswerClone.Answers.Add(answerClone);
			}
		}

		/// <summary>
		/// Copy only the answers which would only apply to the new version
		/// </summary>
		/// <param name="activeQVId">The active questionnaire version's id</param>
		private void CopyValidAnswersForNewVersion(QuestionnaireAnswer qaClone, int activeQVId)
		{
			QuestionnaireVersion activeQV = new QuestionnaireVersion(CachePolicy);
			activeQV.Load(Utility.DataAccess.ConnectionString, activeQVId);
			Serialization.Form form = activeQV.GetForm(this);
			form.DeriveAnswers(CachePolicy, qaClone, true);
		}
		#endregion

		#region ICloneable member
		/// <summary>
		/// Deep Copy the Questionnaire object
		/// </summary>
		/// <param name="checkActiveQuestionnaireVersion">
		/// If set to true, the clone of the Questionnaire Answer will have the corresponding Questionnaire's active Version Id
		///</param>
		/// <returns>A clone of the current QuestionnaireAnswer</returns>
		public QuestionnaireAnswer Clone(bool checkActiveQuestionnaireVersion)
		{
			QuestionnaireAnswer questionnaireAnswerClone = (QuestionnaireAnswer)MemberwiseClone();
			questionnaireAnswerClone.IsClone = true;
			questionnaireAnswerClone.Action = (int)UserAction.Edit;
			questionnaireAnswerClone.Id = 0;
			questionnaireAnswerClone.Dirty = true;
			questionnaireAnswerClone.Answers = new ArrayList();
			questionnaireAnswerClone.Highlight = false;
			if (!string.IsNullOrEmpty(questionnaireAnswerClone.Status))
				questionnaireAnswerClone.Status = FormsManagement.Constants.QuestionnaireAnswerStatus.Start;
			if (!checkActiveQuestionnaireVersion)
			{
				CopyAnswers(questionnaireAnswerClone, Answers);
			}
			else
			{
				int activeVersionId = Questionnaire.GetActiveVersion(CachePolicy, questionnaireVersionId).Id;
				if (activeVersionId == questionnaireVersionId)
				{
					CopyAnswers(questionnaireAnswerClone, Answers);
				}
				else
				{
					questionnaireAnswerClone.QuestionnaireVersionId = activeVersionId;
					CopyValidAnswersForNewVersion(questionnaireAnswerClone, activeVersionId);
				}
			}
			return questionnaireAnswerClone;
		}
		#endregion

		#region ICloneable Members
		/// <summary>
		/// Explicit interface method implementation - available for 
		/// clients of ICloneable, but invisible to other
		/// clients of QuestionnaireAnswer class
		/// </summary>
		/// <returns>QuestionnaireAnswer clone as a generic object</returns>
		object ICloneable.Clone()
		{
			return Clone(false);
		}
		#endregion

		/// <summary>
		/// Actions the user is taking on this questionnaireAnswer
		/// </summary>
		public enum UserAction
		{
			NoChange = 1, // no change being made 
			Edit = 2, // this will be modified
			Remove = 3 // this is being removed from the study
		}
	}
}
